package panda.client;

import java.util.ArrayList;
import java.util.List;

import panda.client.engine.ClientModel;
import panda.client.engine.MagicCard;
import panda.client.engine.ServerModel;
import panda.client.enumerations.CardScale;
import panda.client.enumerations.ClientID;
import panda.client.enumerations.Color;
import panda.client.enumerations.MagicSet;
import panda.client.listeners.PandaKeyDownHandler;
import panda.client.listeners.PandaKeyUpHandler;
import panda.client.services.DataService;
import panda.client.services.DataServiceAsync;
import panda.client.ui.widgets.ActionBar;
import panda.client.ui.widgets.CombatZone;
import panda.client.ui.widgets.Counter;
import panda.client.ui.widgets.DraggableCard;
import panda.client.ui.widgets.NoteTip;
import panda.client.ui.widgets.TokenSummonDialog;
import panda.client.ui.widgets.Tooltip;
import panda.client.ui.widgets.ZoomManager;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.RootPanel;

public class Cardpanda implements EntryPoint {
	public static FocusPanel focusPnl;
	public static AbsolutePanel canvas;
	public static TokenSummonDialog tokenMenu;
	public static ActionBar actionBar;
	public static ZoomManager zoomManager;
	public static int canvasHeight;
	public static final long doubleClickThreshold = 300;
	public static final boolean P2MIRROR = true; /* set to true to mirror P2's client: P2 = bottom */
	public static final boolean CLICK_TO_TAP = true; /* click quickly to tap, click and hold to drag */
	public static ClientModel clientModel;
	public static ServerModel initialBlankModel; /* for restoring the game state */
	
	private static long startTime = (long) JSNI.getCurrentMs();
	private static boolean initialized = false;
	
	/**
	 * Create a remote service proxy to talk to the server-side Greeting service.
	 */
	private final static DataServiceAsync dataServiceAsync = GWT.create(DataService.class);

	public static long getElapsedMsSinceStart() {
		return (long) JSNI.getCurrentMs() - startTime;
	}

	public void onModuleLoad() {
		System.out.println("[c] start time: " + getElapsedMsSinceStart());

		focusPnl = new FocusPanel();
		canvas = new AbsolutePanel();
		tokenMenu = new TokenSummonDialog();

		focusPnl.addKeyDownHandler(new PandaKeyDownHandler());
		focusPnl.addKeyUpHandler(new PandaKeyUpHandler());

		/* the #canvas div will surround the focusPnl and hide its dotted line selection. */
		canvasHeight = RootPanel.get("canvas").getOffsetHeight() - 2; /* 1 for #canvas's top and bottom border */
		canvas.setHeight(canvasHeight + "px");

		/* attack zone */
		CombatZone zone = new CombatZone();
		canvas.add(zone, 0, canvasHeight / 2 - zone.getStripeHeight() / 2);
		/* end attack zone code */

		focusPnl.add(canvas);

		registerClient();
		addWindowCloseHandler();
	}

	/**
	 * Temporarily disabled. When the browser window is closed, terminate the server. This will prevent orphaned
	 * connections until I level up as a GWT dev and find a more elegant solution.
	 */
	private void addWindowCloseHandler() {
		Window.addCloseHandler(new CloseHandler<Window>() {
			public void onClose(CloseEvent<Window> event) {
				System.out.println("Window closed.");
			}
		});
	}

	private void registerClient() {
		AsyncCallback<ClientID> callback = new AsyncCallback<ClientID>() {
			public void onSuccess(ClientID result) {
				ClientID sessionID = (ClientID) result;
				System.out.println("[c] registered " + sessionID + ". Now retrieving model from server.");

				clientModel = new ClientModel(sessionID);
				zoomManager = new ZoomManager(clientModel.getObsLastMousedCard());

				addActionBar();
				addTokenCreator();

				RootPanel.get("canvas").add(focusPnl);

				loadNoteTipPanel();

				Tooltip.get().setVisible(false);
				canvas.add(Tooltip.get(), 0, 0);

				receiveModelFromServer();
			}

			public void onFailure(Throwable caught) {
				System.out.println("failure in registerClient");
				caught.printStackTrace();
			}
		};

		dataServiceAsync.registerClient("this string will be used later to let the client input his own ID", callback);
	}

	/** Sets the focus on cardpanda's main focus panel so key events can be 'seen' */
	public static void setFocus() {
		focusPnl.setFocus(true);
	}

	private void receiveModelFromServer() {
		AsyncCallback<ServerModel> callback = new AsyncCallback<ServerModel>() {
			public void onSuccess(ServerModel model) {
				System.out.println("[c] " + clientModel.getSessionID() + ": received model " + model.getModelNum()
						+ " from server");
				clientModel.syncWithServerModel(model);

				/* the first time a model is received, broadcast to other clients that this client logged in. */
				/* this section must be called sync */
				if(!initialized) {
					initialized = true;
					initialBlankModel = model;

					/* 2008-07-22 1pm prefetch all images in both libraries */
					List<MagicCard> prefetchCards = new ArrayList<MagicCard>();
					prefetchCards.addAll(model.getP1lib());
					prefetchCards.addAll(model.getP2lib());

					for(MagicCard card : prefetchCards) {
						Image.prefetch(card.getImgUrl());
						Image.prefetch(card.getTapUrl());
						Image.prefetch(card.getUrl50());
						Image.prefetch(card.getUrl75());
						Image.prefetch(card.getZoomUrl());
					}
					/* end prefetch */

					String name = Cardpanda.clientModel.getSessionID().getName();
					String logForMe = "You log in as " + name + ".";
					String logForOthers = name + " has logged in.";
					NoteTip.get().log(logForMe, logForOthers);
					sendModelUpdateToServer(clientModel.getSessionID());
				}

				/* Set up the next call in the infinite loop of getting updates and waiting until the server has updates */
				receiveModelFromServer();
			}

			public void onFailure(Throwable caught) {
				System.out.println("failure in getClientUpdates... not going to retry");

				caught.printStackTrace();
				// receiveModelFromServer();
			}
		};

		dataServiceAsync.getModel(clientModel.getSessionID(), callback);
	}

	public static void sendModelUpdateToServer(final ClientID ID) {
		/* the callback! */
		AsyncCallback<Void> callback = new AsyncCallback<Void>() {
			public void onSuccess(Void result) {
				// result is null since updateModel is a void method
				System.out.println("[c] " + ID + "'s server update was successful");
			}

			public void onFailure(Throwable caught) {
				System.out.println("failure in updateServer");
				caught.printStackTrace();
			}
		};

		dataServiceAsync.updateModel(ID, clientModel.createServerModelCopy(), callback);

		/**
		 * Increment model number AFTER the updateModel call is made. This is ok because if the model update fails on the
		 * server due to concurrent modification, the server returns its latest model (which will have the correct model
		 * number). If the model update succeeds, then the server will have incremented its model number as well.
		 */
		clientModel.incrementModelNumber();
	}

	/** Grabs a fresh serverID from the server for counter creation. */
	public static void createCounter(final Color color, final List<Counter> counters, final int x, final int y) {
		AsyncCallback<Long> callback = new AsyncCallback<Long>() {
			public void onSuccess(Long result) {
				Long serverID = (Long) result;
				System.out.println("[c] receiving serverID " + result + ".");

				Counter createdCounter = new Counter(color, serverID);

				counters.add(createdCounter);
				Cardpanda.canvas.add(createdCounter, x, y);

				String logForMe = "You add a counter.";
				String logForOthers = Cardpanda.clientModel.getSessionID().getName() + " adds a counter.";
				NoteTip.get().log(logForMe, logForOthers);

				Cardpanda.sendModelUpdateToServer(Cardpanda.clientModel.getSessionID());
			}

			public void onFailure(Throwable caught) {
				System.out.println("failure in createCounter");
				caught.printStackTrace();
			}
		};

		dataServiceAsync.getNewServerID(callback);
	}

	/* adds the token creator dialog to the canvas (default hidden) */
	private void addTokenCreator() {
		canvas.add(tokenMenu, 200, 200);
	}

	/* initialize and add the actionbar */
	private void addActionBar() {
		actionBar = new ActionBar();
		RootPanel.get("actionbar").add(actionBar);
	}

	/* initialize and add the noteTip panel: panel currently encloses noteTip with a margin-top */
	private void loadNoteTipPanel() {
		// HorizontalPanel noteTipPnl = new HorizontalPanel();
		// DOM.setStyleAttribute(noteTipPnl.getElement(), "marginTop", "3px");
		// RootPanel.get("statusArea").add(noteTipPnl);

		// noteTipPnl.add(NoteTip.get());
		RootPanel.get("statusArea").add(NoteTip.get());
	}

	/**
	 * Queries the server for a MagicCard with MagicSet='set' and ID='id'. For code reability purposes, the name is also
	 * a parameter, and this method double checks that the name matches the name of the query result. Updates the server
	 * afterwards.
	 */
	public static void grabTokenByExactIDFromServer(final String name, MagicSet set, int id) {
		AsyncCallback<MagicCard> callback = new AsyncCallback<MagicCard>() {
			public void onSuccess(MagicCard result) {
				MagicCard card = result;

				/* must make sure token has an owner */
				card.setOwner(clientModel.getSessionID());

				DraggableCard draggable = new DraggableCard(card);
				clientModel.addCardInPlay(draggable, 40, Cardpanda.canvasHeight / 2 - CardScale.QUARTER.getHeight() / 2);
				System.out.println(draggable.getName() + " added!");

				String logForMe = "You reveal " + draggable.getName() + ".";
				String logForOthers = clientModel.getSessionID().getName() + " reveals " + draggable.getName() + ".";
				NoteTip.get().log(logForMe, logForOthers);
				Cardpanda.sendModelUpdateToServer(Cardpanda.clientModel.getSessionID());
			}

			public void onFailure(Throwable caught) {
				System.out.println("failure in grabTokenByExactIDFromServer: " + name);
				// caught.printStackTrace();
			}
		};

		dataServiceAsync.getCardViaExactID(name, set, id, callback);
	}
}